package org.restoran.service.impl;

import org.restoran.model.*;
import org.restoran.repository.OrderRepository;
import org.restoran.repository.TableRepository;
import org.restoran.service.OrderService;
import org.restoran.service.parser.Parser;
import org.restoran.util.Guards;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.Writer;
import java.util.*;

import static com.google.common.base.Preconditions.*;


/**
 *
 * @author Snisar Bogdan.
 */
@Repository
@Service("orderService")
@Transactional (propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public class OrderServiceImpl implements OrderService {

    private static Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);


    @Autowired
    public OrderServiceImpl(Parser parser,
                            OrderRepository orderRepository,
                            TableRepository tableRepository) {
        this.parser = parser;
        this.orderRepository = orderRepository;
        this.tableRepository = tableRepository;
    }

    private final Parser parser;

    private final OrderRepository orderRepository;
    private final TableRepository tableRepository;

    @Override
    public Order findOne(Integer integer)
    {
        return orderRepository.findOne(integer);
    }

    @Override
    public Iterable<Order> findAll()
    {
        return orderRepository.findAll();
    }

    @Override
    public Order save(Order entity)
    {
        checkNotNull(entity, "Entity order must not be null");
        checkArgument(entity.isNew(), "Try save not new entity");
        checkArgument(entity.getStatus() != OrderStatus.CLOSED, "Use uncorrect status for save new Order");

        Order validatedOrder = createAndAddOrderToTable(entity);
        return orderRepository.save(validatedOrder);
    }

    private Order createAndAddOrderToTable(Order order)
    {
        logger.debug("create new order with given data: {}", order);

        if(order != null) {
            Guards.OrderServiceGuard.checkTableInOrder(order, tableRepository);

            Integer tableId = order.getTable().getId();
            Table persisted = tableRepository.findOne(tableId);
            order.addTable(persisted);

            return order;
        } else {
            return new Order();
        }
    }

    @Override
    public Order updateExisted(Order forUpdate)
    {
        checkArgument(!forUpdate.isNew(), "Try update not existed order");

        logger.debug("update order from entity {}", forUpdate);

        Integer orderId = forUpdate.getId();
        Order orderFromDb = orderRepository.findOne(orderId);
        orderFromDb.setCloseDate(forUpdate.getCloseDate());

        Table table = forUpdate.getTable();
        orderFromDb.setTable(table);

        margeItemOrders(orderFromDb.getItems(), forUpdate.getItems());
        return orderRepository.save(orderFromDb);
    }

    private void margeItemOrders(Set<ItemOrder> toUpdate, Set<ItemOrder> values)
    {
        toUpdate.retainAll(values);
        updateItemsSet(toUpdate, values);
    }

    private void updateItemsSet(Set<ItemOrder> toUpdate, Set<ItemOrder> values)
    {
        for(ItemOrder io : values)
        {
            if(!toUpdate.add(io))
            {

                for(ItemOrder updated : toUpdate)
                {
                    if(io.equals(updated))
                    {
                        updateItemOrder(updated, io);
                        break;
                    }

                }
            }
        }
    }

    @Override
    public Order closeOrder(Order order)
    {
        checkArgument(order.getStatus() == OrderStatus.CLOSED, "Order on close must be with correct status");
        checkArgument(!order.isNew(), "Close order with no id");

        logger.debug("update order {} from status CLOSED, clean Table and delete Order", order);

        Integer orderId = order.getId();
        Order persistOrder = orderRepository.findOne(orderId);

        Table currentTable = persistOrder.getTable();
        currentTable.setStatus(TableStatus.CLOSED);

        orderRepository.delete(orderId);
        return order;
    }

    private void updateItemOrder(ItemOrder updatable, ItemOrder data) {
        updatable.setCount(data.getCount());
    }

    @Override
    public void delete(Order deleted) {
        orderRepository.delete(deleted);
    }

    @Override
    public void delete(Integer deletedId) {
        orderRepository.delete(deletedId);
    }


    @Override
    public String parseToJson(Order entity) {
        return parser.toJson(entity);
    }

    @Override
    public void parseToJson(Writer writer, Order entity) {
        parser.toJson(writer, entity);
    }

    @Override
    public String parseToJson(Collection<Order> objects) {
        return parser.toJson(objects);
    }

    @Override
    public void parseToJson(Writer writer, Collection<Order> args) {
       parser.toJson(writer, args);
    }

    @Override
    public Order parseFromJson(String json, Class<Order> type) {

        Order or = parser.fromJson(json, Order.class);
        for(ItemOrder io : or.getItems()) {
            io.setOrder(or);
        }
        return or;

    }

}
